Juan M. Fonseca-Solís · Agosto 2020 · 5 min read
Los estetoscopios digitales son herramientas que le permiten a los médicos realizar remotamente un seguimiento de sus pacientes por problemas de salud relacionados, por ejemplo, con el corazón y los pulmones. En muchos de los casos, estos estetoscopios digitales utilizan los mismos codecs que son empleados en los sistemas de teleconferencia de Zoom, MS Teams y Skype. A pesar de mostrar un excelente rendimiento para transmitir voz y música con bajas tasas de bits, estos codecs eliminan frecuencias que son inaudibles para el oído humano y que podrían ser necesarias por los algoritmos de reconocimiento de patrones. En este ipython notebook nos dimos a la tarea de realizar pruebas al códec OPUS para determinar si la pérdida de la calidad es realmente significativa. Los resultados mostraron que...
El codificador de audio OPUS es el códec más empleado en la actualidad por los sistemas de teleconferencia por su capacidad de transmitir mejor calidad de audio a bajas tasas de bits en comparación a otros códecs como el MP3, AAC, Vorbis (ver figura 1). Está compuesto por dos algoritmos llamados SILK (creado por Skype Limited para comprimir voz) y Constrained Energy Lapped Transform (CELT) (para comprimir música) [3, 4, 13]. La figura 1 muestra el rango de operación del OPUS en términos de bits por segundo y una comparación respecto otros codecs.
Figura 1. Tomada de https://opus-codec.org/static/comparison/quality.svg.
La mayoría de códecs de sonido utilizan la teoría psicoacústica para "engañar" al oído humano eliminando las frecuencias inaudibles. Para ello, utilizan conceptos tales como las escalas Bark y ERB, bandas críticas, enmascaramiento frecuencial, enmascaramiento temporal y predicción lineal (LPC, por sus siglas en Inglés). A continuación hacemos un resumen de estos conceptos:
Figura 2. Tomada de https://en.wikipedia.org/wiki/Auditory_system#/media/File:Anatomy_of_the_Human_Ear.svg.
%pylab inline
import numpy as np
F = np.linspace(0,20000,1000)
erb = 21.4 * np.log10(0.00437*F+1)
pylab.plot(F/1e3,erb)
pylab.ylabel('Frequency (kHz)')
pylab.xlabel('ERB (kHz)')
pylab.show()
Bandas críticas: segmentos de la cóclea, de diferente longitud y localización, en los cuales el oído humano no es capaz de diferenciar dos tonos lo suficientemente cercanos. Tres ejemplos de bandas críticas están en los rangos $[9, 12]$ kHz, $[12, 15]$ kHz y $[15, 20]$ kHz [10].
Enmascaramiento frecuencial: fenómeno producido las bandas críticas (ver figura 3) donde la frecuencia de mayor energía enmascara a las de menor energía mediante su envolvente espectral.
Figura 3. Tomada de https://output.com/blog/9-sound-design-tips-to-hack-your-listeners-ears.
Enmascaramiento temporal: fenómeno que ocurre cuando dos eventos ocurren lo suficientemente cercanos en el tiempo y el más fuerte enmascará al más débil si el segundo ocurre 10 ms antes (pre-enmascaramiento) o 30-60 ms después (post-enmascaramiento) que el primero [7] (ver figura 4).
Figura 4. Tomada de https://d3i71xaburhd42.cloudfront.net/13674722d0e4fc8a6877773b25a62ee9850eb46a/3-Figure2-1.png.
Basándonos en los conceptos de psicoacústica explicados arriba, podemos definir una serie de casos de prueba que el algoritmo OPUS tendría que superar para ser útil para un estetoscopio digital.
Para probar el caso 2 se puede utilizar un barrido de frecuencias que siga la siguiente ecuación [6] $$ F(t) = \Big(\frac{F_1-F_0}{T}\Big)t + F_0, $$
El rango de frecuencias elegido fue $[20, 22.5K]$ Hz, que es el mismo rango admitido por los equipos electrónicos comerciales y también el rango de audición humana. El barrido puede ser construido usando un senoidal de la forma $x[t] = A \sin(2\pi Ft)$.
Para probar el caso 1, se puede agregar al barrido un tono adicional con energía más baja:
$$ x[t] = \sin(2\pi F_0 t) + 0.2 \sin(2\pi 1.1 F_0 t). $$%pylab inline
from scipy.io import wavfile
from IPython.display import Audio
import numpy as np
def plotSpecgram(x,Fs):
# spectrogram
fig, ax = pylab.subplots(nrows=1)
ax.specgram(x, NFFT=1024, Fs=Fs, noverlap=900)
pylab.xlabel('Tiempo (s)')
pylab.ylabel('Frecuencia (Hz)')
pylab.show()
def plotFFT(x,Fs):
pylab.figure()
N2=int(len(x)/2)
F = np.linspace(0,Fs/2,N2)/1000
X = np.sqrt(np.abs(np.fft.fft(x)[0:N2]))
pylab.plot(F, X)
X[0] = 0 # remove DC value
pylab.xlabel('Frecuencia (kHz)')
pylab.ylabel('$\sqrt{|S(f)|}$')
pylab.show()
rango = [20.0, 20000.0] # el rango promedio de audicion humano en Hz
Fs = rango[1]*3 # 2 veces la freq. maxima para cumplir con el teorema del muestreo
T = 1.0 # segundos (t1-t0)
N = int(T*Fs)
n = np.arange(0,N)
F0 = (rango[1]-rango[0])*n/N + rango[0]
x = np.sin(np.pi*F0/Fs*n) + 0.2*np.sin(np.pi*1.05*F0/Fs*n) # f=F0/Fs (discrete frequency)
plotSpecgram(x,Fs)
plotFFT(x,Fs)
wavfile.write('/tmp/barrido_20_20k.wav',int(Fs),x.astype(np.float32))
Audio(x, rate=Fs)
La magnitud espectral de la señal de audio es una recta en el rango $[20, 20000]$ Hz, lo que significa que todas las frecuencias están presentes como era de esperar.
Procedemos a codificar y decodificar el barrido de frecuencias usando OPUS para una tasa de bits de 8 Kbps (la misma empleada en una videollamada entre dos personas) [12].
def readPlayVisualizeFile(inputFile):
fs, x = wavfile.read(inputFile)
y = np.array(x)/max(x)
plotSpecgram(x,fs)
return fs, x
# MP3
#!sudo apt-get install lame
#!lame -b8 --quiet /tmp/barrido_20_20k.wav /tmp/opusEnc.mp3
#!lame --quiet --decode /tmp/opusEnc.mp3 /tmp/opusDec.wav
#!ls -sh /tmp/opusDec.wav
# OPUS
#!sudo apt-get install ffmpeg
#!sudo apt-get install opus-tools
!ffmpeg -loglevel error -y -i /tmp/barrido_20_20k.wav -qscale 0 /tmp/wavRaw.wav # opus-tools
!opusenc --quiet --bitrate 8 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusenc --quiet --bitrate 8 /tmp/barrido_20_20k.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Como se observa, las frecuencias más altas que 5K Hz fueron truncadas completamente. Probemos ahora con 128 kbps (fullband stereo) a ver si la calidad mejora.
# MP3
!ffmpeg -loglevel error -y -i /tmp/barrido_20_20k.wav -qscale 0 /tmp/wavRaw.wav # opus-tools
!opusenc --quiet --bitrate 128 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusenc --quiet --bitrate 128 /tmp/barrido_20_20k.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Observamos que el contenido frecuencial ha sufrido muchos cambios, entre ellos, se introdujeron frecuencias parásitas producto del aliasing o del clipping), hay al menos 4 rectas principales en lugar de 2, también parece que se ha aplicado un filtro pasabajas con frecuencia de corte 20K Hz. Entonces sí que el codec Opus introduce distorsiones. Esto significa que si se aplica reconocimiento de patrones a una grabación procesada por Opus, el clasificador tendría información con mucho más ruido que con una grabación en formato WAV.
Aún se pueden apreciar los dos tonos ubicados cerca de 10K Hz, por lo que la distorsión introducida para el Opus no ha sido mucha. Cabe preguntarse, ¿esto sucede porque se está usando CELT en lugar de SILK? ¿Podría forzarse usar SILK?
Nota: a partir de este momento se recomienda usar audifonos para apreciar mejor la calidad el audio.
Ahora realicemos una prueba procesando una grabación empleada en el reconocimiento de sonidos del cuerpo (una disciplina llamada auscultación). Para tomamos una grabación del sitio https://www.kaggle.com/vbookshelf/respiratory-sound-database.
Fs, x = readPlayVisualizeFile('./wav/107_3p2_Tc_mc_AKGC417L_2.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Observamos que hay energía alta en el rango $[0,1.5]$ y $[7.5,9.5]$ kHz.
Al parecer, la cantidad de bits usados tiene un efecto directamente proporcional sobre la frecuencia de corte. Además, observamos que la energía del rango $[0,1.5]$ se dispersó ahora por $[0,4.5]$ kHz y que el pic en $[7.5,9.5]$ kHz desapareció.
# OPUS
!ffmpeg -loglevel error -y -i ./wav/107_3p2_Tc_mc_AKGC417L_2.wav -qscale 0 /tmp/wavRaw.wav # same quality
!opusenc --quiet --bitrate 8 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
En términos psicoacústicos, la distorsión del OPUS no parece estar en el enmascaramiento de frecuencias sino más bien en la presencia de contenido frecuencial que no estaba antes, producto de un fenómeno llamado clipping. Igualmente, esto podría afectar el rendimiento de un algoritmo de reconocimiento de patrones.

Este obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial-SinObraDerivada 4.0 Internacional. El sitio juanfonsecasolis.github.io es un blog dedicado a la investigación independiente en temas relacionados al procesamiento digital de señales. Para reutilizar este artículo y citar las fuente por favor utilice el siguiente Bibtex:
@online{Fonseca2020,
author = {Juan M. Fonseca-Solís},
title = { Pruebas por pares o pairwise testing},
year = 2020,
url = {https://juanfonsecasolis.github.io/blog/JFonseca.pairwisetesting.html},
urldate = {}
}